use std::rc::Rc;

use cgmath::{vec2, Matrix4, SquareMatrix, Vector2, Vector4};

use tetris_core::Board;
use wgpu::{include_wgsl, vertex_attr_array};

use super::{
    framework::{
        material::Material, mesh::MeshInstance, shader::Shader, uniform::Uniform, Attribute,
        Context,
    },
    get_val_color, AsBytes, Vertex, QUAD_SIZE,
};

#[repr(C)]
#[derive(Clone, Copy)]
pub struct Vertex2DC {
    position: [f32; 2],
    color: [f32; 4],
}

impl Vertex for Vertex2DC {
    const ATTRS: &'static [wgpu::VertexAttribute] =
        &vertex_attr_array![0 => Float32x2, 1 => Float32x4];
}
impl AsBytes for Vertex2DC {}

pub fn v2dc(position: Vector2<f32>, color: Vector4<f32>) -> Vertex2DC {
    Vertex2DC {
        position: [position.x, position.y],
        color: [color.x, color.y, color.z, color.w],
    }
}

pub fn v2dcq(quad: [Vector2<f32>; 4], color: Vector4<f32>) -> [Vertex2DC; 4] {
    [
        v2dc(quad[0], color),
        v2dc(quad[1], color),
        v2dc(quad[2], color),
        v2dc(quad[3], color),
    ]
}

pub struct Renderer {
    material: Material,
    mesh: MeshInstance<Vertex2DC>,
}

impl Renderer {
    pub fn new(ctx: &Context, global_layout: &wgpu::BindGroupLayout) -> Self {
        let shader =
            Shader::with_vertex_screen::<Vertex2DC>(ctx, include_wgsl!("../../shaders/board.wgsl"));

        let uniform = Uniform::new(ctx, wgpu::ShaderStages::VERTEX, Matrix4::identity());

        let pipeline_layout = ctx.create_pipeline_layout(&[global_layout, uniform.layout()]);

        let pipeline = ctx.create_simple_render_pipeline(
            &pipeline_layout,
            &shader,
            wgpu::PrimitiveState {
                front_face: wgpu::FrontFace::Cw,
                cull_mode: Some(wgpu::Face::Back),
                ..Default::default()
            },
        );

        let mut material = Material::new(Rc::new(pipeline));
        material.put(1, Box::new(uniform));

        let mesh = MeshInstance::new(ctx);

        Self { material, mesh }
    }

    pub fn sync(&mut self, board: &Board) {
        self.mesh.clear();
        board.visit(|x, y, val| {
            //if val == 0 { return; }
            let origin = vec2(x as f32, y as f32);

            let rt = origin + 0.5 * QUAD_SIZE;
            let lb = origin - 0.5 * QUAD_SIZE;
            let rb = lb + vec2(QUAD_SIZE.x, 0.0);
            let lt = lb + vec2(0.0, QUAD_SIZE.y);

            let color = get_val_color(val);

            self.mesh.add_quad(v2dcq([lb, lt, rt, rb], color));
        })
    }

    pub fn set_position(&mut self, position: Vector2<f32>) {
        self.material
            .attr_mut::<Uniform<Matrix4<f32>>>(1)
            .unwrap()
            .set_data(Matrix4::from_translation(position.extend(0.0)));
    }

    pub fn prepare(&mut self, ctx: &Context) {
        self.material.prepare(ctx);
        self.mesh.prepare(ctx);
    }

    pub fn render<'p>(&'p self, pass: &mut wgpu::RenderPass<'p>) {
        self.material.set(pass);
        self.mesh.render(pass);
    }
}
